#include <network\streamingClient.h>

#include <commands\client\c_send_login.h>
#include <commands\client\c_request_model_description.h>

#include <app\states\cacheState.h>
#include <app\states\connectionState.h>
#include <app\states\modelInfoState.h>

streamingClient *streamingClient::_instance = NULL;

void 
streamingClient::destroy() {
  if (_instance) delete _instance;
}

streamingClient &
streamingClient::instance() {
  if (!_instance) {
    _instance = new streamingClient;
  }
  return *_instance;
}

bool
streamingClient::getModelDescription() {
  
  std::cerr << "receiving model description" << endl;

  // get connection state
  connectionState *cstate = GET_STATE(connectionState, new_connection);
  cacheState *cachestate = GET_STATE(cacheState, new_connection);

  // create model dir
  Poco::Path model_dir;
  model_dir.pushDirectory(cachestate->getCacheDir().toString());
  model_dir.pushDirectory(cstate->getModelName());
  model_dir.pushDirectory(cstate->getUserName());
  Poco::File file(model_dir);
  file.createDirectories();

  model_dir.pushDirectory("trajectory_tiles");
  file = model_dir;
  file.createDirectories();

  model_dir.popDirectory();

  model_dir.pushDirectory("terrain_tiles");
  file = model_dir;
  file.createDirectories();
  model_dir.popDirectory();

  model_dir.pushDirectory("texture_tiles");
  file = model_dir;
  file.createDirectories();
  model_dir.popDirectory();

  model_dir.pushDirectory("context_tiles");
  file = model_dir;
  file.createDirectories();
  model_dir.popDirectory();

  model_dir.pushDirectory("geometry_tiles");
  file = model_dir;
  file.createDirectories();
  model_dir.popDirectory();
  
  // create model info state
  modelInfoState *mfinfostate = GET_STATE(modelInfoState, new_connection);

  // request model info
  c_request_model_description *cmd = static_cast<c_request_model_description *>(commandFactory::instance().createCommand(CLIENT_REQUEST_MODEL_DESCRIPTION, new_connection));
  cmd->execute();

  // wait until modelinfostate is received
  while (!mfinfostate->modelInfoReceived()) Sleep(1);

  return true;
}

void 
streamingClient::setCacheDir(string cachedir) {
  std::cerr << "setting cache directory: " << cachedir << endl;
  cacheState *chstate = GET_STATE(cacheState, new_connection);
  chstate->setCacheDir(cachedir);
}

void 
streamingClient::setLogin(string modelname, string username, string password) {

    std::cerr << "login: " << modelname << " " << username << " " << password << endl;

   connectionState *cstate = GET_STATE(connectionState, new_connection);
   cstate->setModelName(modelname);
   cstate->setUserName(username);
   cstate->setPassword(password);
}

void 
streamingClient::setConnection(string server, string port) {
  tcp::resolver::query query(server, port);
  endpoint_iterator = resolver->resolve(query);
}

void 
streamingClient::start() {

  tcp::socket *socket_ = &new_connection->socket();
  tcp::endpoint endpoint = *endpoint_iterator;

  socket_->async_connect(endpoint,
    boost::bind(&streamingClient::handle_connect, this, new_connection,
      boost::asio::placeholders::error, endpoint_iterator));
  
  t = new boost::thread(boost::bind(&boost::asio::io_service::run, &io_service));
}

bool
streamingClient::isInitialized() {
   
  connectionState *cstate = GET_STATE(connectionState, new_connection);
  while (cstate->waitForConnection()) Sleep(1);
  
  // wait for establishing connection to server
  if (!cstate->isConnectionEstablished()) {
    std::cerr << "connection not established" << endl;
    return false;
  }

  // request login
  c_send_login *cmd = static_cast<c_send_login *>(commandFactory::instance().createCommand(CLIENT_SEND_LOGIN, new_connection));
  cmd->setData(cstate->getModelName().c_str(), cstate->getUserName().c_str(), cstate->getPassword().c_str());
  cmd->execute();

  // wait for login acception
  while (cstate->waitForLogin()) Sleep(1);
  
  if (!cstate->isLoginAccepted()) {
    std::cerr << "login rejected" << endl;
    return false;
  }
  std::cerr << "login accepted" << endl;

  // request model description
  return getModelDescription();
}

void 
streamingClient::stop() {
  io_service.stop();
  t->join();
}

streamingClient::streamingClient() {
  resolver = new tcp::resolver(io_service);
  
  new_connection = new streamingConnection(io_service);
}

streamingClient::~streamingClient() {
  delete new_connection;
  delete resolver;
  delete t;
}

streamingConnection *
streamingClient::connection() {
  return new_connection;
}

void 
streamingClient::handle_connect(streamingConnection* new_connection, const boost::system::error_code& error, tcp::resolver::iterator endpoint_iterator) {
  
  connectionState *cstate = GET_STATE(connectionState, new_connection);
  
  if (!error) {

    new_connection->start();

    cstate->setConnectionEstablished(true);
  }
  else if (endpoint_iterator != tcp::resolver::iterator()) {
    cstate->setConnectionEstablished(false);
  }
}
